Skip to content

[6.x] Legacy Javascript Compatibility#18618

Draft
brianjhanson wants to merge 49 commits into6.xfrom
feature/js-compat
Draft

[6.x] Legacy Javascript Compatibility#18618
brianjhanson wants to merge 49 commits into6.xfrom
feature/js-compat

Conversation

@brianjhanson
Copy link
Copy Markdown
Contributor

@brianjhanson brianjhanson commented Mar 24, 2026

This brings some compatibility between the legacy javascript and our new inertia and Vue world. The result of all of this is a functional Section settings page which is initially rendered via Inertia but calls legacy code in order to render the slideouts and nested slideouts.

CleanShot.2026-04-08.at.14.33.30.mp4

CpAssetBundle

The CpAssetBundle is now loaded in both the Inertia and Twig render paths. Twig works as it always has. On the Inertia side, the HandleInertiaRequests middleware will register the CpAssetBundle and then inject the headHtml and bodyHtml into the app.blade.php template. That will throw the same old Craft global variable you know and love onto the page. With the variable on the page, when the cp.js file gets loaded by its position in bodyHtml it will populate the global variable with all the methods we've always had.

This means you can call your Craft.* Javascript just like you always have. The one caveat is that it's more possible than previously to create click handlers, etc. for content that will be loaded in after the fact.

At the moment you have both the legacy Craft global variable and the new Cp variable. Over time, we'll move methods and configuration from Craft to Cp .

Styling

Styling presented a bit of a challenge as we can have both old and new markup on a page at one time (if a slideout with legacy content is rendered for example). This can make styles a bit messy in some places, but I've done my best to isolate things.
The TailwindResetAsset will now only apply styles to .cp-legacy-reset, .slideout-container, .menu--disclosure and (almost) all legacy styles will only kick in when wrapped with a .cp-legacy class.
This falls down a bit for modals, menus, huds, etc. because we render those at the document root instead of in a container where we can add the cp-legacy class. Those styles are still applied globally for now.

Potential Breaking Changes

  • Javascript registerred with the {% js %} twig tags will now result in a <script type="module"> tag. In theory, this shouldn't break anything because the load order will still be respected, but it is a shift.
  • Event delegation is recommended so you can ensure your click handlers, etc. will fire even if the markup they expect isn't on the page when the handler is initially registerred.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR bridges Craft’s legacy CP JavaScript/CSS behavior into the new Inertia + Vue CP so that Inertia-rendered pages (notably Section settings) can still open/use legacy slideouts and nested UI.

Changes:

  • Inject CP legacy asset output (headHtml/bodyHtml) into the Inertia root view and add a composable for appending/deduping injected HTML.
  • Scope/reset legacy styling via .cp-legacy* classes and adjust legacy UI components (slideouts/HUDs/menus) to work alongside new markup.
  • Add/adjust resources + Vue UI for Entry Type selection, plus tests around input namespacing and script tag generation.

Reviewed changes

Copilot reviewed 87 out of 111 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
yii2-adapter/package-lock.json Adds optional dependency metadata (lockfile churn).
yii2-adapter/legacy/web/assets/tailwindreset/src/tailwind_reset.css Scopes Tailwind base reset to legacy containers via @scope.
yii2-adapter/legacy/web/assets/tailwindreset/dist/css/tailwind_reset.css Built output reflecting scoped reset.
yii2-adapter/legacy/web/assets/tailwindreset/dist/css/tailwind_reset.css.map Source map update for scoped reset.
yii2-adapter/legacy/web/assets/pluginstore/dist/js/app.js.LICENSE.txt Updates bundled license text (ApexCharts version/license header change).
yii2-adapter/legacy/web/assets/cp/src/js/SortableCheckboxSelect.js Re-init handling + disclosure trigger lookup tweaks.
yii2-adapter/legacy/web/assets/cp/src/js/Slideout.js Adds .cp-legacy classes to slideout containers.
yii2-adapter/legacy/web/assets/cp/src/js/FieldLayoutDesigner.js Adds .cp-legacy HUD class; attempts to init SortableCheckboxSelect.
yii2-adapter/legacy/web/assets/cp/src/css/craft.scss Reorganizes legacy CSS imports and wraps most legacy styles under .cp-legacy.
yii2-adapter/legacy/web/assets/cp/src/css/_slideouts.scss New/isolated slideout styling.
yii2-adapter/legacy/web/assets/cp/src/css/_menu.scss New/isolated menu styling.
yii2-adapter/legacy/web/assets/cp/src/css/_hud.scss New/isolated HUD/modal styling.
yii2-adapter/.stylelintrc.json Disables no-invalid-position-at-import-rule rule globally.
tests/Unit/View/InputNamespaceTest.php Adds test for excluding name attributes on non-form elements.
tests/Unit/View/HtmlStackTest.php Updates expectation for module script output.
tests/Unit/Support/HtmlTest.php Adds fixtures ensuring excluded elements aren’t namespaced.
tests/Unit/Cp/IconsTest.php Adds tests for icon family/path/data resolution helpers.
src/View/HtmlStack.php Emits {% js %} output as <script type="module">…</script>.
src/Support/Html.php Excludes slot/craft-icon tags from namespacing name= attributes.
src/Section/Resources/SectionResource.php Serializes entry types via EntryTypeResource collection.
src/Providers/AppServiceProvider.php Disables JsonResource wrapping globally; minor URL facade import adjustments.
src/Http/Middleware/HandleInertiaRequests.php Registers CP assets + injects headHtml/bodyHtml into Inertia root view.
src/Http/Controllers/Settings/EntryTypesController.php Adds resource response for override settings + casts ID input to int.
src/Entry/Resources/EntryTypeResource.php New JSON resource for entry type data incl. actions/indicators/icon metadata.
src/Entry/Data/EntryType.php Uses event delegation for slideout action binding (dynamic DOM compatibility).
src/Cp/Icons.php Adds resolve helpers (family/path/data) and refactors svg loading to reuse them.
src/Cp/Html/ElementHtml.php Switches chip rendering to custom elements (craft-chip, craft-icon) + slot-based layout.
resources/views/app.blade.php Inertia root view now includes injected legacy asset HTML.
resources/templates/_layouts/base.twig Adds legacy classes + legacy globals injection for Twig CP path.
resources/templates/_includes/forms/entry-type-select/selection-settings.twig Updates container markup + uses delegated dblclick/taphold handler.
resources/templates/_includes/disclosuremenu.twig Switches invoker to craft-button with icon/appearance props.
resources/js/types/index.ts Updates EntryType/icon/action/indicator typing for new payload shapes.
resources/js/types/globals.d.ts Consolidates legacy Craft typings into a global types file.
resources/js/pages/SettingsSectionsEditPage.vue Adjusts entry type form model + prop names for new EntryTypeSelect API.
resources/js/layout/AppLayout.vue Adds (currently-unused) useAppendHtml() call.
resources/js/Craft.d.ts Removes old temporary Craft typings file.
resources/js/composables/useReorderableRows.ts Migrates row reordering to new generic drag/drop composable.
resources/js/composables/useReorderableItems.ts New generic reorderable-items composable.
resources/js/composables/useDragAndDrop.ts New generic drag/drop implementation using Atlaskit pragmatic DnD.
resources/js/composables/useAppendHtml.ts New wrapper composable around CP DOM append utilities.
resources/js/components/Tooltip/Tooltip.vue New Vue tooltip wrapper using c-tooltip.
resources/js/components/ReorderButton.vue Adds cursor: grab styling.
resources/js/components/Pane.vue Renames pane BEM classes to cp-pane* variants.
resources/js/components/form/EntryTypeSelect.vue Major refactor: chip rendering, drag-reorder, legacy slideout override settings, create button.
resources/js/components/EntryType/EntryTypeChip.vue New chip UI for entry types (actions/indicators/drag handle).
resources/js/components/EntryType/CreateEntryTypeButton.vue New “Create” button that opens legacy screen slideout and restores focus.
resources/js/components/DropIndicator.vue Expands indicator behavior/props and styling.
resources/js/components/DragShadow.vue New drag placeholder/shadow component.
resources/js/components/ActionMenu.vue Tweaks action item binding (id/icon/variant/click handling).
resources/build/useFetch.js Build artifact updates (hashed imports).
resources/build/useEditableTable.js Build artifact updates (hashed imports).
resources/build/Updater.js Build artifact updates (hashed imports).
resources/build/SettingsSitesIndex.js Build artifact updates (pagination param + hashed imports).
resources/build/SettingsSectionsIndexPage.js Build artifact updates (pagination param + hashed imports).
resources/build/SettingsIndexPage.js Build artifact updates (hashed imports).
resources/build/Pane.js Build artifact updates matching cp-pane* rename.
resources/build/nav-list.ts.js Build artifact updates (hashed import).
resources/build/nav-list-Cg7ivPRt.js New build artifact for nav list component.
resources/build/nav-item.ts.js Build artifact updates (hashed import).
resources/build/nav-item-ChCEIDF_.js Build artifact updates (hashed import).
resources/build/ModalForm.js Build artifact updates (hashed imports).
resources/build/manifest.json Manifest updated for new hashed chunk names and entries.
resources/build/legacy.js Build artifact updates (hashed deps + runtime helper name change).
resources/build/IndexLayout.js Build artifact updates (hashed imports).
resources/build/DeleteSiteModal.js Build artifact updates (hashed imports).
resources/build/decorate-EBysIGtV.js New build artifact chunk.
resources/build/CpQueueIndicator.js Build artifact updates (hashed import).
resources/build/CpGlobalSidebar.js Build artifact updates (hashed import).
resources/build/CalloutReadOnly.js Build artifact updates (hashed import).
resources/build/assets/SettingsSectionsEditPage.css Build artifact updates for new entry type chip/dnd styles.
resources/build/assets/Pane.css Build artifact updates for cp-pane* styles.
resources/build/assets/AppLayout.css Build artifact updates (scoped hash changes).
resources/build/assets/AdminTable.css Build artifact updates for drop indicator + reorder button cursor.
resources/build/AppLayout.js Build artifact updates (includes inlined append HTML helper).
packages/craftcms-cp/src/utilities/icons.ts Adjusts custom icon resolution to consider family as well as variant.
packages/craftcms-cp/src/utilities/dom.ts New DOM utilities for appending/deduping injected head/body HTML.
packages/craftcms-cp/src/utilities/dom.test.ts New tests for DOM append/dedup behavior.
packages/craftcms-cp/src/styles/shared/colorable.css Regenerates color classes to cp-color-* and adds semantic colors.
packages/craftcms-cp/src/styles/shared/base.css Adds .cp-code and adjusts hr to logical properties.
packages/craftcms-cp/src/index.ts Exports new DOM utilities.
packages/craftcms-cp/src/components/tooltip/tooltip.ts Tooltip styling changes (WebAwesome tooltip).
packages/craftcms-cp/src/components/tooltip/tooltip.stories.ts Story switches to <c-tooltip>.
packages/craftcms-cp/src/components/chip/chip.ts Chip markup/class changes + icon slot support.
packages/craftcms-cp/src/components/chip/chip.styles.ts Chip styles updated for new cp-chip* classes.
packages/craftcms-cp/src/components/button/button.ts Adds TODO comment placeholder for status slot/region.
packages/craftcms-cp/scripts/generate-colors.js Updates generator to produce cp-color-* + semantic colors.
resources/js/composables/useTableDragAndDrop.ts Removes old table-specific drag/drop composable.
Files not reviewed (1)
  • yii2-adapter/package-lock.json: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +53 to +67
#[Override]
public function handle(Request $request, Closure $next): Response
{
Craft::$app->view->registerAssetBundle(CpAsset::class);
$htmlStack = app(HtmlStack::class);

View::composer('app', function ($view) use ($htmlStack) {
$view->with([
'headHtml' => $htmlStack->headHtml(),
'bodyHtml' => $htmlStack->bodyHtml(),
]);
});

return parent::handle($request, $next);
}
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

View::composer('app', ...) is being registered inside handle(), which runs on every request. In long-running PHP environments (Octane/Swoole/RoadRunner) this will accumulate composers over time (memory/perf leak) and can cause headHtml() / bodyHtml() to be computed multiple times per render. Register the composer once (e.g. in a service provider boot method), or replace with a per-request View::share()/$request->attributes approach without global registration.

Copilot uses AI. Check for mistakes.
Comment on lines +904 to +916
// Elements that use name= for non-form purposes
$excludedElements = ['slot', 'craft-icon'];

$html = preg_replace_callback(
'/<('.implode('|', $excludedElements).')\b[^>]*>/i',
function (array $match) use (&$markers): string {
$marker = sprintf('{ns-marker:%s}', mt_rand());
$markers[$marker] = $match[0];

return $marker;
},
$html
) ?? $html;
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The marker generation uses mt_rand() and doesn’t guarantee uniqueness. A collision would overwrite a previous entry in $markers, causing incorrect strtr() restoration (HTML corruption). Use a collision-resistant marker (e.g. incrementing counter, Str::uuid(), or bin2hex(random_bytes())) and/or loop until the marker key is unused.

Copilot uses AI. Check for mistakes.
Comment on lines 92 to 97
onCheck: function () {
this.$actionMenuBtn = this.$item.find('[data-disclosure-trigger]');

if (this.$actionMenuBtn) {
this.onUncheck();
}
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.$actionMenuBtn is a jQuery object; if (this.$actionMenuBtn) will always be truthy even when it’s empty. As written, onCheck() will always call onUncheck(), which can remove/destroy state unnecessarily. Check .length (or similar) before treating the element as present.

Copilot uses AI. Check for mistakes.
Comment on lines +294 to 303
<template v-for="entryType in selectableTypes" :key="entryType.id">
<craft-action-item
@click="handleTypeSelect(type)"
@click="handleTypeSelect(entryType)"
type="checkbox"
:icon="type.icon ?? 'empty'"
:checked="modelValue.includes(type.id)"
:data-color="type.color?.value ?? 'white'"
:icon="entryType.icon ?? 'empty'"
:checked="
modelValue.find((valueType) => valueType.id === entryType.id)
"
:data-color="entryType.color?.value ?? 'white'"
>
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<craft-action-item>’s icon property is defined as string | null (it renders <craft-icon name="${icon}">). Here entryType.icon is an object (name/family/variant), so binding it to :icon will result in a non-string value and likely render incorrectly. Pass a string icon name (e.g. entryType.icon?.name) or provide a <craft-icon slot="icon" ...> instead.

Copilot uses AI. Check for mistakes.
Comment on lines +52 to +57
description: string | null;
icon?: null | {
name: string;
family: string;
variant: string;
};
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EntryType.icon is typed as an object with required name, family, and variant, but the backend resource currently returns only name and family (no variant). This mismatch will either break TypeScript or force runtime undefined values when spreading into <craft-icon v-bind="icon" />. Align the API payload and the TS type (either make variant optional or include it in the resource output).

Copilot uses AI. Check for mistakes.
Comment on lines 40 to +43
watch(errorFlash, (newMessage) => announce(newMessage));

useAppendHtml();

Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useAppendHtml() is called without a source object and its return value is unused, so it currently has no effect (the internal watch() only runs when source is provided). Either pass the Inertia props that contain headHtml/bodyHtml, or remove this call to avoid dead code.

Copilot uses AI. Check for mistakes.
Comment on lines +9 to +15
/**
* Composable for appending HTML to the document head or body.
*
* Handles deduplication of CSS and JS resources automatically,
* ensuring that stylesheets and scripts are only loaded once.
* headHtml is always loaded and executed before bodyHtml.
*
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring says “headHtml is always loaded and executed before bodyHtml”, but appendHeadHtml() / appendBodyHtml() only append nodes; they don’t await external <script src> load/execution. If consumers rely on execution order (especially for legacy globals), this comment is misleading. Either adjust the docs to “appended before” or update the DOM utility to await script load events when needed.

Copilot uses AI. Check for mistakes.
Comment on lines 9 to 12
"no-descending-specificity": null,
"no-duplicate-selectors": null,
"no-invalid-position-at-import-rule": [
true,
{
"ignoreAtRules": ["tailwind", "use"]
}
],
"no-invalid-position-at-import-rule": null,
"selector-class-pattern": null,
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting no-invalid-position-at-import-rule to null disables the check project-wide, which can allow real @import ordering issues to slip in unnoticed. If this was done to accommodate specific at-rules (e.g. Tailwind/Sass), consider keeping the rule enabled and adjusting ignoreAtRules instead so you still get protection for plain CSS @import ordering mistakes.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants